;
; xenix memory calls for MSDOS
;
; CAUTION: The following routines rely on the fact that arena_signature and
; arena_owner_system are all equal to zero and are contained in DI.
;
INCLUDE DOSSEG.ASM

CODE    SEGMENT BYTE PUBLIC  'CODE'
        ASSUME  SS:DOSGROUP,CS:DOSGROUP

.xlist
.xcref
INCLUDE DOSSYM.ASM
INCLUDE DEVSYM.ASM
.cref
.list

TITLE ALLOC.ASM - memory arena manager
NAME Alloc

SUBTTL memory allocation utility routines
PAGE
;
; arena data
;
        i_need  arena_head,WORD         ; seg address of start of arena
        i_need  CurrentPDB,WORD         ; current process data block addr
        i_need  FirstArena,WORD         ; first free block found
        i_need  BestArena,WORD          ; best free block found
        i_need  LastArena,WORD          ; last free block found
        i_need  AllocMethod,BYTE        ; how to alloc first(best)last

;
; arena_free_process
; input:    BX - PID of process
; output:   free all blocks allocated to that PID
;
        procedure   arena_free_process,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     DI,arena_signature
        MOV     AX,[arena_head]
        CALL    Check_Signature         ; ES <- AX, check for valid block

arena_free_process_loop:
        retc
        PUSH    ES
        POP     DS
        CMP     DS:[arena_owner],BX     ; is block owned by pid?
        JNZ     arena_free_next         ; no, skip to next
        MOV     DS:[arena_owner],DI     ; yes... free him

arena_free_next:
        CMP     BYTE PTR DS:[DI],arena_signature_end
                                        ; end of road, Jack?
        retz                            ; never come back no more
        CALL    arena_next              ; next item in ES/AX carry set if trash
        JMP     arena_free_process_loop

arena_free_process  ENDP

;
; arena_next
; input:    DS - pointer to block head
; output:   AX,ES - pointers to next head
;           carry set if trashed arena
;
        procedure   arena_next,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     AX,DS                   ; AX <- current block
        ADD     AX,DS:[arena_size]      ; AX <- AX + current block length
        INC     AX                      ; remember that header!
;
;       fall into check_signature and return
;
;       CALL    check_signature         ; ES <- AX, carry set if error
;       RET
arena_next  ENDP

;
; check_signature
; input:    AX - address of block header
; output:   ES=AX, carry set if signature is bad
;
        procedure   check_signature,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     ES,AX                   ; ES <- AX
        CMP     BYTE PTR ES:[DI],arena_signature_normal
                                        ; IF next signature = not_end THEN
        JZ      check_signature_ok      ;   GOTO ok
        CMP     BYTE PTR ES:[DI],arena_signature_end
                                        ; IF next signature = end then
        JZ      check_signature_ok      ;   GOTO ok
        STC                             ; set error
        return

check_signature_ok:
        CLC
        return
Check_signature ENDP

;
; Coalesce - combine free blocks ahead with current block
; input:    DS - pointer to head of free block
; output:   updated head of block, AX is next block
;           carry set -> trashed arena
;
        procedure   Coalesce,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CMP     BYTE PTR DS:[DI],arena_signature_end
                                        ; IF current signature = END THEN
        retz                            ;   GOTO ok
        CALL    arena_next              ; ES, AX <- next block, Carry set if error
        retc                            ; IF no error THEN GOTO check

coalesce_check:
        CMP     ES:[arena_owner],DI
        retnz                           ; IF next block isnt free THEN return
        MOV     CX,ES:[arena_size]      ; CX <- next block size
        INC     CX                      ; CX <- CX + 1 (for header size)
        ADD     DS:[arena_size],CX      ; current size <- current size + CX
        MOV     CL,ES:[DI]              ; move up signature
        MOV     DS:[DI],CL
        JMP     coalesce                ; try again
Coalesce    ENDP

SUBTTL $Alloc - allocate space in memory
PAGE
;
;   Assembler usage:
;           MOV     BX,size
;           MOV     AH,Alloc
;           INT     21h
;         AX:0 is pointer to allocated memory
;         BX is max size if not enough memory
;
;   Description:
;           Alloc returns  a  pointer  to  a  free  block of
;       memory that has the requested  size  in  paragraphs.
;
;   Error return:
;           AX = error_not_enough_memory
;              = error_arena_trashed
;
        procedure   $ALLOC,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING

        XOR     AX,AX
        MOV     DI,AX

        MOV     [FirstArena],AX         ; init the options
        MOV     [BestArena],AX
        MOV     [LastArena],AX

        PUSH    AX                      ; alloc_max <- 0
        MOV     AX,[arena_head]         ; AX <- beginning of arena
        CALL    Check_signature         ; ES <- AX, carry set if error
        JC      alloc_err               ; IF error THEN GOTO err

alloc_scan:
        PUSH    ES
        POP     DS                      ; DS <- ES
        CMP     DS:[arena_owner],DI
        JZ      alloc_free              ; IF current block is free THEN examine

alloc_next:
        CMP     BYTE PTR DS:[DI],arena_signature_end
                                        ; IF current block is last THEN
        JZ      alloc_end               ;   GOTO end
        CALL    arena_next              ; AX, ES <- next block, Carry set if error
        JNC     alloc_scan              ; IF no error THEN GOTO scan

alloc_err:
        POP     AX

alloc_trashed:
        error   error_arena_trashed

alloc_end:
        CMP     [FirstArena],0
        JNZ     alloc_do_split

alloc_fail:
        invoke  get_user_stack
        POP     BX
        MOV     [SI].user_BX,BX
        error   error_not_enough_memory

alloc_free:
        CALL    coalesce                ; add following free block to current
        JC      alloc_err               ; IF error THEN GOTO err
        MOV     CX,DS:[arena_size]

        POP     DX                      ; check for max found size
        CMP     CX,DX
        JNA     alloc_test
        MOV     DX,CX

alloc_test:
        PUSH    DX
        CMP     BX,CX                   ; IF BX > size of current block THEN
        JA      alloc_next              ;   GOTO next

        CMP     [FirstArena],0
        JNZ     alloc_best
        MOV     [FirstArena],DS         ; save first one found
alloc_best:
        CMP     [BestArena],0
        JZ      alloc_make_best         ; initial best
        PUSH    ES
        MOV     ES,[BestArena]
        CMP     ES:[arena_size],CX      ; is size of best larger than found?
        POP     ES
        JBE     alloc_last
alloc_make_best:
        MOV     [BestArena],DS          ; assign best
alloc_last:
        MOV     [LastArena],DS          ; assign last
        JMP     alloc_next

;
; split the block high
;
alloc_do_split_high:
        MOV     DS,[LastArena]
        MOV     CX,DS:[arena_size]
        SUB     CX,BX
        MOV     DX,DS
        JE      alloc_set_owner         ; sizes are equal, no split
        ADD     DX,CX                   ; point to next block
        MOV     ES,DX                   ; no decrement!
        DEC     CX
        XCHG    BX,CX                   ; bx has size of lower block
        JMP     alloc_set_sizes         ; cx has upper (requested) size

;
; we have scanned memory and have found all appropriate blocks
; check for the type of allocation desired; first and best are identical
; last must be split high
;
alloc_do_split:
        CMP     BYTE PTR [AllocMethod], 1
        JA      alloc_do_split_high
        MOV     DS,[FirstArena]
        JB      alloc_get_size
        MOV     DS,[BestArena]
alloc_get_size:
        MOV     CX,DS:[arena_size]
        SUB     CX,BX                   ; get room left over
        MOV     AX,DS
        MOV     DX,AX                   ; save for owner setting
        JE      alloc_set_owner         ; IF BX = size THEN (don't split)
        ADD     AX,BX
        INC     AX                      ; remember the header
        MOV     ES,AX                   ; ES <- DS + BX (new header location)
        DEC     CX                      ; CX <- size of split block
alloc_set_sizes:
        MOV     DS:[arena_size],BX      ; current size <- BX
        MOV     ES:[arena_size],CX      ; split size <- CX
        MOV     BL,arena_signature_normal
        XCHG    BL,DS:[DI]              ; current signature <- 4D
        MOV     ES:[DI],BL              ; new block sig <- old block sig
        MOV     ES:[arena_owner],DI

alloc_set_owner:
        MOV     DS,DX
        MOV     AX,[CurrentPDB]
        MOV     DS:[arena_owner],AX
        MOV     AX,DS
        INC     AX
        POP     BX
        transfer    SYS_RET_OK

$alloc  ENDP

SUBTTL $SETBLOCK - change size of an allocated block (if possible)
PAGE
;
;   Assembler usage:
;           MOV     ES,block
;           MOV     BX,newsize
;           MOV     AH,setblock
;           INT     21h
;         if setblock fails for growing, BX will have the maximum
;         size possible
;   Error return:
;           AX = error_invalid_block
;              = error_arena_trashed
;              = error_not_enough_memory
;              = error_invalid_function
;
        procedure   $SETBLOCK,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     DI,arena_signature
        MOV     AX,ES
        DEC     AX
        CALL    check_signature
        JNC     setblock_grab

setblock_bad:
        JMP     alloc_trashed

setblock_grab:
        MOV     DS,AX
        CALL    coalesce
        JC      setblock_bad
        MOV     CX,DS:[arena_size]
        PUSH    CX
        CMP     BX,CX
        JBE     alloc_get_size
        JMP     alloc_fail
$setblock   ENDP

SUBTTL $DEALLOC - free previously allocated piece of memory
PAGE
;
;   Assembler usage:
;           MOV     ES,block
;           MOV     AH,dealloc
;           INT     21h
;
;   Error return:
;           AX = error_invalid_block
;              = error_arena_trashed
;
        procedure   $DEALLOC,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        MOV     DI,arena_signature
        MOV     AX,ES
        DEC     AX
        CALL    check_signature
        JC      dealloc_err
        MOV     ES:[arena_owner],DI
        transfer    SYS_RET_OK

dealloc_err:
        error   error_invalid_block
$DEALLOC    ENDP

SUBTTL $AllocOper - get/set allocation mechanism
PAGE
;
;   Assembler usage:
;           MOV     AH,AllocOper
;           MOV     BX,method
;           MOV     AL,func
;           INT     21h
;
;   Error return:
;           AX = error_invalid_function
;
        procedure   $AllocOper,NEAR
        ASSUME  DS:NOTHING,ES:NOTHING
        CMP     AL,1
        JB      AllocOperGet
        JZ      AllocOperSet
        error   error_invalid_function
AllocOperGet:
        MOV     AL,BYTE PTR [AllocMethod]
        XOR     AH,AH
        transfer    SYS_RET_OK
AllocOperSet:
        MOV     [AllocMethod],BL
        transfer    SYS_RET_OK
$AllocOper  ENDP

do_ext

CODE    ENDS
    END
         